package codec

import (
	"fmt"
	"strconv"

	"github.com/pkg/errors"
)

// ErrorCode error has message and code
type ErrorCode interface {
	// Error returns the error code string
	Error() string
	// Message returns the human-readable error string
	Message() string
	// Code returns the actual error code
	Code() int
	// FatherCode returns the father code of this error
	FatherCode() int
	// RootCode returns the root code of this error
	RootCode() int
	// WithError wrap with error
	WithError(e error) ErrorCode
	// Wrap wraps messages
	Wrap(str string) error
	// Name returns name
	Name() string
}

// CodeFields represents the fields of code
type CodeFields map[string]interface{}

// Code represents the Error Code
type Code struct {
	codec  int
	fields CodeFields
}

// use to implement basic namespace, the space for a father
// code is within 1000. Like the code of err1 is 1, the err2
// with code 2 is child of err1, the real code of err2 will
// be 1 * 1000 + 2
const codecBase = 1000

var (
	// messageBase saves code -> message
	messageBase = make(map[int]string)
	// codeSet use for unique check in code
	codeSet = make(map[int]bool)
	// codeFather use to find the CodeRoot
	codeFather = make(map[int]int)
	// nameBase use to find the name
	nameBase = make(map[int]string)
)

func (c Code) Error() string {
	return fmt.Sprintf("%d: %s", c.Code(), c.Message())
}

// Message is the message of error
func (c Code) Message() string {
	var msg string

	fcList := c.FatherCodeList()
	// prepend all message
	for _, fc := range fcList {
		msg += messageBase[fc] + ": "
	}

	msg += messageBase[c.codec]
	for key, val := range c.fields {
		msg += fmt.Sprintf(" %s=%v", key, val)
	}
	return msg
}

// Name returns the name of code
func (c Code) Name() string {
	return nameBase[c.codec]
}

// RawMessage returns the message without fileds
func (c Code) RawMessage() string {
	return messageBase[c.codec]
}

// Code is the codec of error
func (c Code) Code() int {
	return c.codec
}

// FatherCode returns the code's father
func (c Code) FatherCode() int {
	return codeFather[c.codec]
}

// FatherCodeList return the list of all father
func (c Code) FatherCodeList() []int {
	var fcList []int
	fc := c.codec
	for fc != codeFather[fc] {
		fc = codeFather[c.codec]
		fcList = append([]int{fc}, fcList...)
	}
	return fcList
}

// RootCode returns the code of root
func (c Code) RootCode() int {
	fc := c.codec
	for fc != codeFather[fc] {
		fc = codeFather[fc]
	}
	return fc
}

// WithFields return code with fields
func (c Code) WithFields(fields CodeFields) Code {
	newC := Code{
		codec:  c.codec,
		fields: make(CodeFields),
	}

	for key, val := range c.fields {
		newC.fields[key] = val
	}
	for key, val := range fields {
		newC.fields[key] = val
	}

	return newC
}

// WithField return code with field
func (c Code) WithField(key string, val interface{}) Code {
	return c.WithFields(CodeFields{
		key: val,
	})
}

// WithError wraps with another error
func (c Code) WithError(e error) ErrorCode {
	return c.WithField("error", e.Error())
}

// Wrap wraps messages
func (c Code) Wrap(str string) error {
	return errors.Wrap(c, str)
}

// NewChild new a new Error Code Of Child
func (c Code) NewChild(codec int, message string) Code {
	realCodec := c.Code()*codecBase + codec
	// update codec with basis
	code := New(realCodec, message)
	codeFather[code.codec] = c.codec
	return code
}

// New news a new Error Code
func New(codec int, message string) Code {
	_, ok := codeSet[codec]
	if ok {
		panic("codec has duplicated: " + strconv.Itoa(codec))
	}

	code := Code{
		codec:  codec,
		fields: make(CodeFields),
	}
	codeSet[codec] = true
	messageBase[codec] = message
	codeFather[codec] = codec
	return code
}

// IsErrorEqual return wether e is code and equal to c
func (c Code) IsErrorEqual(e error) bool {
	ec, ok := e.(Code)
	if !ok {
		return false
	}
	return c.codec == ec.codec
}
